﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace DFlowCore
{
    public class Network
    {
        public interface IListener
        {

            // BASIC INPUT INTERFACE ASSUME A REQ-REP PROTOCOL USE NO REPLY IF NOREPLY IS GIVEN TO A QUERY
            bool HasIncomingPacket();
            byte[] FetchPacket();
            void SendPacket(byte[] b);
            void NoReply();

            // THE ASYNCHRONOUS CHANNEL IS SETUP BY THE NORMAL PROTOCOL AT THE HELLO TIME
            // COMMUNICATION MODE IS PUSH PULL
            void SetAsyncSocket(string url);
            void SendAsyncPacket(byte[] b,object iep);
            void SendAsyncUDPPacket(byte[] b,object iep);            
            object GetCurrentRemoteEndpoint();
            bool IsConnectedTo(object iep);

        }


        public class UDPListener : IListener
        {
            System.Net.Sockets.Socket skt;
            IPEndPoint serverep;
            EndPoint lastclient;
            IPEndPoint asyncsocketep;
            //System.Net.Sockets.Socket async_skt;

            public UDPListener(string server_url)
            {
                skt = null;
                skt = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                char[] seps = { ':', '/' };
                string servername = (server_url.Substring(6).Split(seps))[0];
                int serverport = int.Parse(server_url.Substring(6).Split(seps)[1]);
                IPAddress ipa=null;
                serverep = new IPEndPoint((System.Net.IPAddress.TryParse(servername, out ipa) ? ipa : System.Array.Find(System.Net.Dns.GetHostEntry(servername).AddressList, x => (x.AddressFamily == AddressFamily.InterNetwork))), serverport);
                skt.Bind(serverep);
                lastclient = new IPEndPoint(new IPAddress(0), 0);
            }


            public bool HasIncomingPacket()
            {
                return skt.Poll(0, SelectMode.SelectRead);
            }


            public byte[] FetchPacket()
            {
                byte[] b = new byte[65535];
                int nbuf = -1;
                int maxretries = 3;
                while (nbuf <= 0)
                {
                    if (maxretries < 0)
                    {
                        throw new System.Exception("Error while fetching packet");
                    }


                    nbuf = skt.ReceiveFrom(b, ref lastclient);

                    maxretries--;
                }
                System.Array.Resize(ref b, nbuf);
                return b;
            }


            public void SendPacket(byte[] b)
            {
                if (b.Length >= 65507)
                {
                    throw new System.Exception("Message too long");
                }
                bool sent = false;
                int maxretries = 3;
                while (!sent)
                {
                    if (maxretries < 0)
                    {
                        throw new System.Exception("Error while sending packet");
                    }
                    if (skt.SendTo(b, lastclient) == b.Length) { sent = true; };
                    maxretries--;
                }
            }


            public void SendAsyncPacket(byte[] b,object _iep)
            {
                IPEndPoint iep = (IPEndPoint)_iep;
                if (iep==null) {iep=asyncsocketep;}
                if (b.Length >= 65507)
                {
                    throw new System.Exception("Message too long");
                }
                bool sent = false;

                int maxretries = 3;
                while (!sent)
                {
                    if (maxretries < 0)
                    {
                        throw new System.Exception("Error while sending packet");
                    }
                    if (skt.SendTo(b, iep) == b.Length) { sent = true; };
                    maxretries--;
                }
            }

            public void SendAsyncUDPPacket(byte[] b,object _iep)
            {
                IPEndPoint iep = (IPEndPoint)_iep;
                if (b.Length >= 65507)
                {
                    throw new System.Exception("Message too long");
                }
                bool sent = false;

                int maxretries = 3;
                while (!sent)
                {
                    if (maxretries < 0)
                    {
                        throw new System.Exception("Error while sending packet");
                    }
                    if (skt.SendTo(b, iep) == b.Length) { sent = true; };
                    maxretries--;
                }
            }

            public object GetCurrentRemoteEndpoint()
            {
                return asyncsocketep;
            }


            public void NoReply()
            {
            }

            public void SetAsyncSocket(string url)
            {
                char[] seps = { ':', '/' };
                string servername = (url.Substring(6).Split(seps))[0];
                int serverport = int.Parse(url.Substring(6).Split(seps)[1]);
                IPAddress ipa = null;
                asyncsocketep = new IPEndPoint((System.Net.IPAddress.TryParse(servername, out ipa) ? ipa : System.Array.Find(System.Net.Dns.GetHostEntry(servername).AddressList, x => (x.AddressFamily == AddressFamily.InterNetwork))), serverport);

                //asyncsocketep = new IPEndPoint(System.Array.Find(System.Net.Dns.GetHostEntry(servername).AddressList, x => (x.AddressFamily == AddressFamily.InterNetwork)), serverport);
            }


            public bool IsConnectedTo(object iep)
            {
                return true;
            }
        }

        // Length Prefixed Messages Over TCP
        public class LETCPListener : IListener
        {
            System.Net.Sockets.Socket base_skt;
            public int replyto_skt_idx;
            System.Net.Sockets.Socket replyto_skt;
            System.Collections.Generic.List<System.Net.Sockets.Socket> cnx_skts;
            IPEndPoint serverep;
            System.Collections.Generic.List<System.Net.Sockets.Socket> async_skts;
            //System.Collections.Generic.List<string> async_skts_url;
            System.Collections.Generic.List<IPEndPoint> async_skts_url;
            System.Net.Sockets.Socket base_aync_skt;


            public System.Net.Sockets.Socket GetAsyncSocket(int i)
            {
                if ((async_skts[i] == null) || (!async_skts[i].Connected))
                {
                    //char[] seps = { ':', '/' };
                    //string servername = (async_skt_url.Substring(6).Split(seps))[0];
                    //int serverport = int.Parse(async_skt_url.Substring(6).Split(seps)[1]);
                    async_skts[i] = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    try
                    {
                        async_skts[i].Connect(async_skts_url[i]); //new IPEndPoint(System.Net.Dns.Resolve(servername).AddressList[0], serverport));
                        async_skts[i].NoDelay = true;
                    }
                    catch (System.Exception e)
                    {
                        System.Diagnostics.Debug.Print(e.ToString());
                    }
                }
                return async_skts[i];

            }

            System.Net.Sockets.Socket async_skt
            {
                get
                {
                    return GetAsyncSocket(replyto_skt_idx);
                }
                set { async_skts[replyto_skt_idx] = value; }
            }
            IPEndPoint async_skt_url
            {
                get { return async_skts_url[replyto_skt_idx]; }
                set { async_skts_url[replyto_skt_idx] = value; }
            }



            public LETCPListener(string server_url)
            {
                base_skt = null;
                base_skt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                cnx_skts = new System.Collections.Generic.List<System.Net.Sockets.Socket>();
                char[] seps = { ':', '/' };
                string servername = (server_url.Substring(6).Split(seps))[0];
                int serverport = int.Parse(server_url.Substring(6).Split(seps)[1]);
                IPAddress ipa = null;
                serverep = new IPEndPoint((System.Net.IPAddress.TryParse(servername, out ipa) ? ipa : System.Array.Find(System.Net.Dns.GetHostEntry(servername).AddressList, x => (x.AddressFamily == AddressFamily.InterNetwork))), serverport);

                //serverep = new IPEndPoint(System.Array.Find(System.Net.Dns.GetHostEntry(servername).AddressList, x => (x.AddressFamily == AddressFamily.InterNetwork)), serverport);
                base_skt.Bind(serverep);
                base_skt.Listen(5);
                base_skt.NoDelay = true;

                async_skts = new List<Socket>();
                async_skts_url = new List<IPEndPoint>();

                base_aync_skt = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                //async_skt.Bind(new IPEndPoint(System.Net.Dns.Resolve(servername).AddressList[0], serverport));
            }

            public void SetAsyncSocket(string url)
            {
                char[] seps = { ':', '/' };
                string servername = (url.Substring(6).Split(seps))[0];
                int serverport = int.Parse(url.Substring(6).Split(seps)[1]);


                async_skt_url = new IPEndPoint(System.Array.Find(System.Net.Dns.GetHostEntry(servername).AddressList, x => (x.AddressFamily == AddressFamily.InterNetwork)), serverport);
                //async_skt.Connect(new IPEndPoint(System.Net.Dns.Resolve(servername).AddressList[0],serverport));
            }

            public bool HasIncomingPacket()
            {
                // close in error sockets
                List<int> toberemoved = new List<int>();
                for (int i = 0; i < cnx_skts.Count; i++)
                {
                    if ((!cnx_skts[i].Connected) || cnx_skts[i].Poll(0, SelectMode.SelectError))
                    {
                        toberemoved.Add(i);
                    }
                }
                toberemoved.Reverse();
                foreach (int a in toberemoved) { cnx_skts.RemoveAt(a); async_skts.RemoveAt(a); async_skts_url.RemoveAt(a); }

                if (base_skt.Poll(0, SelectMode.SelectRead))
                {
                    Socket s = base_skt.Accept();
                    //s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.ReceiveLowWater, 4);
                    s.NoDelay = true;
                    cnx_skts.Add(s);
                    async_skts.Add(null);
                    async_skts_url.Add(s.RemoteEndPoint as IPEndPoint);
                    //async_skts_url.Add("tcp://" + ((s.RemoteEndPoint as IPEndPoint).Address.ToString()) + ":6568");
                }

                return cnx_skts.FindAll(skt => skt.Available > 4).Count != 0;
            }


            public byte[] FetchPacket()
            {
                //foreach(Socket skt in cnx_skts.FindAll(skt=>skt.Poll(0,SelectMode.SelectRead))) {
                foreach (Socket skt in cnx_skts.FindAll(skt => skt.Available > 4))
                {
                    if (!skt.Connected)
                        continue;
                    if (skt.Available < 4)
                        continue;

                    byte[] bufsz = new byte[4];
                    int l = skt.Receive(bufsz, 4, SocketFlags.None);
                    if (l != 4)
                    {
                        System.Diagnostics.Debug.Print(System.String.Format("received only {0} bytes instead of 4...", l));
                        cassert(false);
                    }
                    uint len = System.BitConverter.ToUInt32(bufsz, 0);
                    byte[] buffer = new byte[len];
#if NET_DEBUG
                    System.Diagnostics.Debug.Print(System.String.Format("receiving {0} bytes...", len));
#endif
                    l = skt.Receive(buffer, (int)len, SocketFlags.None);
                    if (l != len)
                    {
                        System.Diagnostics.Debug.Print(System.String.Format("received only {0} bytes...", l));
                        cassert(false);
                    }
                    replyto_skt = skt;
                    return buffer;

                }
                return null;
            }


            private void cassert(bool b)
            {
                if (!b)
                {
                    SocketException se;
                    se = new SocketException();
                    //se.Message = "invalid amount of data transfered...";
                    throw se;
                }
            }

            public void SendPacket(byte[] b)
            {
                int maxretries = 3;
                int l = replyto_skt.Send(System.BitConverter.GetBytes((System.UInt32)b.Length));
                cassert(l == 4);

                int remain = b.Length;
#if NET_DEBUG
                System.Diagnostics.Debug.Print(System.String.Format("sending {0} bytes", remain));
#endif
                int offset = 0;
                while (remain > 0)
                {
                    maxretries = 3;
                    int q = -1;
                    while (q == -1)
                    {
                        if (maxretries < 0)
                        {
                            throw new System.Exception("Error while sending packet (i)");
                        }
                        q = replyto_skt.Send(b, offset, remain, SocketFlags.None);

                        maxretries--;
                    }
                    if (q == -1)
                    {
                        throw new System.Exception("Error while sending packet (ii)");
                    }
                    remain -= q;
                    offset += q;
                }
            }


            public void SendAsyncPacket(byte[] b,object _iep)
            {
                IPEndPoint iep = (IPEndPoint)_iep;
                System.Net.Sockets.Socket casync_skt = async_skt;

                if (iep != null)
                {
                    for(int i=0;i<async_skts_url.Count;i++)
                    {
                        if (async_skts_url[i] == iep)
                        {
                            casync_skt = GetAsyncSocket(i);
                        }
                    }
                }

                int maxretries = 3;
                int l = casync_skt.Send(System.BitConverter.GetBytes((System.UInt32)b.Length));

                cassert(l == 4);

                int remain = b.Length;
                //System.Diagnostics.Debug.Print(System.String.Format("sending {0} bytes", remain));
                int offset = 0;
                while (remain > 0)
                {
                    maxretries = 3;
                    int q = -1;
                    while (q == -1)
                    {
                        if (maxretries < 0)
                        {
                            throw new System.Exception("Error while sending packet (i)");
                        }
                        q = casync_skt.Send(b, offset, remain, SocketFlags.None);

                        maxretries--;
                    }
                    if (q == -1)
                    {
                        throw new System.Exception("Error while sending packet (ii)");
                    }
                    remain -= q;
                    offset += q;
                }
            }


            public void SendAsyncUDPPacket(byte[] b, object _iep)
            {
                IPEndPoint iep = (IPEndPoint)_iep;
                if (b.Length > 65007)
                {
                    System.Diagnostics.Debug.Print("[AsyncUDPPacket]too much bytes to send..");
                    return;
                }
                base_aync_skt.SendTo(b,iep);
            }


            public object GetCurrentRemoteEndpoint()
            {
                return replyto_skt.RemoteEndPoint as IPEndPoint;
            }

            public void NoReply() { }

            public bool IsConnectedTo(object iep) {
                return true;
            }
        }


    }
}
